/**
 * 
 */
package xiaojian.toolkit.ebus.unit;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import xiaojian.toolkit.ebus.EventBus;
import xiaojian.toolkit.unit.UnitAdmin;
import xiaojian.toolkit.util.SimpleRegistry;

import bsh.Interpreter;

/**
 * @author hp
 * 
 */
public class UnitGroupManager {

	private static final Logger logger = LoggerFactory
			.getLogger(UnitGroupManager.class);

	private final EventBus eventBus;
	private final SimpleRegistry registry;
	private String[] cfgXmlLocations = new String[] { "buildinUnits/" };

	static private class Tuple {
		public EventUnit unit;
		public Object ctx;
		public long lastModified;

		public Tuple(EventUnit u, Object c, long l) {
			unit = u;
			ctx = c;
			lastModified = l;
		}
	}

	private Map<String, Map<String, Tuple>> groups = new HashMap<String, Map<String, Tuple>>();

	private void doAddUnit(Map<String, Tuple> newer, String key,
			EventUnit unit, long l) {
		newer.put(key, new Tuple(unit, unit.init(eventBus), l));
	}

	private void addUnit(Map<String, Tuple> newer, UnitSource source,
			ApplicationContext rootAppCtx) {

		if (null != newer.get(source.getName())) {
			logger.error("addUnit: key [" + source.getName()
					+ "] already exist.");
			// throw new RuntimeException("key [" + source.getName() +
			// "] already exist.");
			return;
		}

		String sourceType = source.getType();
		if (StringUtils.isEmpty(sourceType)) {
			logger.error(source
					+ "tpye is null, must be either .bsh(BeanShell File) or .xml(Spring Config File) or .cfg(properties file) or .zip(zip unit)");
			return;
		}

		if (sourceType.equals("bsh")) {
			Interpreter inter = new Interpreter();

			try {

				EventUnit unit = (EventUnit) inter.eval(IOUtils.toString(
						source.getInputStream(), "UTF-8"));
				doAddUnit(newer, source.getName(), unit, source.lastModified());
			} catch (Exception e) {
				logger.error("addUnit:", e);
			}

			return;
		}

		if (sourceType.equals("xml")) {
			try {
				doAddUnit(newer, source.getName(), new SpringEventUnitImpl(
						source, rootAppCtx), source.lastModified());
			} catch (Exception e) {
				logger.error("addUnit:", e);
			}

			return;
		}

		if (sourceType.equals("cfg")) {
			try {
				doAddUnit(newer, source.getName(),
						new PropertiesEventUnitImpl(source, cfgXmlLocations)
								.setUnitAdmin((UnitAdmin) this.registry
										.getEntry("__unit_admin")),
						source.lastModified());
			} catch (Exception e) {
				logger.error("addUnit:", e);
			}

			return;
		}
		// else if ( source.getName().endsWith(".jar") ) {
		// if ( logger.isDebugEnabled() ) {
		// logger.debug("found jar unit {}", source.getName());
		// }
		// try {
		// doAddUnit(newer, source.getName(),
		// new JarEventUnitImpl(source, rootAppCtx),
		// source.lastModified() );
		// } catch (Exception e) {
		// logger.error("addUnit:", e);
		// }
		// }

		if (sourceType.equals("zip")) {
			if (logger.isDebugEnabled()) {
				logger.debug("found zip unit {}", source.getName());
			}
			try {
				doAddUnit(newer, source.getName(), new ZipEventUnitImpl(source,
						rootAppCtx), source.lastModified());
			} catch (Exception e) {
				logger.error("addUnit:", e);
			}
			return;
		}

		logger.error(source
				+ " must be either .bsh(BeanShell File) or .xml(Spring Config File) or .cfg(properties file) or .zip(zip unit)");
		// throw new RuntimeException(source +
		// " must be either .bsh(BeanShell File) or .xml(Spring Config File) or .cfg(properties file) or .zip(zip unit)");
	}

	private Map<String, Tuple> getUnitGroup(String groupName) {
		Map<String, Tuple> group = groups.get(groupName);
		if (null == group) {
			group = new HashMap<String, Tuple>();
			groups.put(groupName, group);
		}
		return group;
	}

	private void replaceUnitGroup(String groupName, Map<String, Tuple> newer) {
		groups.put(groupName, newer);
	}

	public void doUnitsChanged(String groupName, Iterator<UnitSource> itr,
			ApplicationContext rootAppCtx) {
		Map<String, Tuple> units = getUnitGroup(groupName);

		Map<String, Tuple> newer = new HashMap<String, Tuple>();
		while (itr.hasNext()) {
			UnitSource source = itr.next();

			Tuple tuple = units.get(source.getName());
			if (null != tuple) {
				if (tuple.lastModified != source.lastModified()) {
					try {
						tuple.unit.destroy(tuple.ctx);
					} catch (Exception e) {
						logger.error("failed to destroy unit {}, bcs of {}",
								tuple.unit.getDescription(), e);
					}
					addUnit(newer, source, rootAppCtx);
					if (logger.isDebugEnabled()) {
						logger.debug("Unit:" + groupName + "."
								+ source.getName() + " updated.");
					}
				} else {
					newer.put(source.getName(), tuple);
					if (logger.isDebugEnabled()) {
						logger.debug("Unit:" + groupName + "."
								+ source.getName() + " !NOT! changed.");
					}
				}
				units.remove(source.getName());
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Unit:" + groupName + "." + source.getName()
							+ " createing.");
				}
				addUnit(newer, source, rootAppCtx);
				if (logger.isDebugEnabled()) {
					logger.debug("Unit:" + groupName + "." + source.getName()
							+ " created.");
				}
			}
		}
		for (Map.Entry<String, Tuple> entry : units.entrySet()) {
			Tuple tuple = entry.getValue();
			try {
				tuple.unit.destroy(tuple.ctx);
			} catch (Exception e) {
				logger.error("failed to destroy unit {}, bcs of {}",
						tuple.unit.getDescription(), e);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Unit:" + groupName + "." + entry.getKey()
						+ " has been removed, so destroy.");
			}
		}
		units.clear();
		replaceUnitGroup(groupName, newer);
	}

	public UnitGroupManager(final EventBus eventBus,
			final SimpleRegistry registry) {
		this.eventBus = eventBus;
		this.registry = registry;
	}

	/**
	 * @return the cfgXmlpath
	 */
	public String[] getCfgXmlLocations() {
		return cfgXmlLocations;
	}

	/**
	 * @param cfgXmlpath
	 *            the cfgXmlpath to set
	 */
	public void setCfgXmlLocations(String[] cfgXmlLocations) {
		this.cfgXmlLocations = cfgXmlLocations;
	}

	public void destroy() {
		for (Map.Entry<String, Map<String, Tuple>> entry : groups.entrySet()) {
			if (logger.isDebugEnabled()) {
				logger.debug("group name is {}", entry.getKey());
			}
			Map<String, Tuple> units = entry.getValue();
			for (Map.Entry<String, Tuple> itr : units.entrySet()) {
				if (logger.isDebugEnabled()) {
					logger.debug("try remove unit {}", itr.getKey());
				}
				Tuple tuple = itr.getValue();

				try {
					tuple.unit.destroy(tuple.ctx);
				} catch (Exception e) {
					logger.error("failed to destroy unit {}, reason {}",
							itr.getKey(), e);
				}
			}
			units.clear();
		}

		groups.clear();
	}

	public Map<String, String> getAllUnitGroup() {

		Map<String, String> ret = new HashMap<String, String>();

		for (Map.Entry<String, Map<String, Tuple>> entry : groups.entrySet()) {

			Map<String, Tuple> units = entry.getValue();
			for (Map.Entry<String, Tuple> itr : units.entrySet()) {
				Tuple tuple = itr.getValue();

				ret.put(entry.getKey() + "-" + itr.getKey(),
						tuple.unit.getDescription() + ":"
								+ new Date(tuple.lastModified));
			}
		}

		return ret;
	}

}
